/*
 * vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker:
 *
 * client.c
 *
 * C.C.Allison
 * chris.allison@hotmail.com
 *
 * Started: Tuesday 25 December 2012, 12:25:36
 * Version: 0.00
 * Revision: $Id: client.c 60 2013-04-05 08:29:40Z chris.charles.allison@gmail.com $
 * Last Modified: Friday  5 April 2013, 08:28:37
 */

#include "client.h"


void setLastPosition( void )/* {{{1 */
{
    /*
     * queries mysql and asks for the last position this server made
     * sets the config items lastlinenumber, lastbytepos, laststamp
     */
    MYSQL_RES *result;
    MYSQL_ROW row;
    my_ulonglong numrows;
    int length;
    char *qbuf=NULL;
    char *tk;
    char *tv;
    int junk;
    // int numfields;
    int myid;

    myid=atoi(configValue("myid"));
    length=snprintf(qbuf,0,"select * from %s where sid=%d order by stamp desc limit 1",configValue("dbtablelog"),myid);
    length++;
    if((qbuf=malloc(length))){
        length=snprintf(qbuf,length,"select * from %s where sid=%d order by stamp desc limit 1",configValue("dbtablelog"),myid);
        DBGL("last pos query: %s",qbuf);
        if((junk=mysql_query(myconn,qbuf))==0){
            if((result=mysql_store_result(myconn))!=NULL){
                numrows=mysql_num_rows(result);
                if(numrows==0){
                    /* no last record */
                    tk=strdup("lastlinenumber");
                    tv=strdup("0");
                    updateConfig(tk,tv);
                    tk=strdup("lastbytepos");
                    tv=strdup("0");
                    updateConfig(tk,tv);
                    tk=strdup("laststamp");
                    tv=strdup("0");
                    updateConfig(tk,tv);
                }else{
                    // numfields = mysql_num_fields(result);
                    row=mysql_fetch_row(result);
                    DBGL("Result: id: %s, sid: %s, stamp: %s, ln: %s, fpos: %s, nstacks: %s", row[0], row[1], row[2], row[3], row[4], row[5]);
                    tk=strdup("laststamp");
                    tv=strdup(row[2]);
                    updateConfig(tk,tv);
                    tk=strdup("lastlinenumber");
                    tv=strdup(row[3]);
                    updateConfig(tk,tv);
                    tk=strdup("lastbytepos");
                    tv=strdup(row[4]);
                    updateConfig(tk,tv);
                }
                mysql_free_result(result);
            }
        }else{
            CCAE(1,"failed to execute last position query");
        }
        free(qbuf);
    }else{
        CCAE(1,"failed to allocate memory for last position query.")
    }
}/* }}} */
void setLogServerId( void )/* {{{1 */
{
    /*
     * queries mysql and asks for my server id
     * if it doesn't exist (because this combination of
     * server and log file hasn't been used before)
     * it attempts to create it.
     */
    MYSQL_RES *result;
    MYSQL_ROW row;
    my_ulonglong numrows;
    my_ulonglong umyid=0;
    int junk;
    int tjunk;
    int length;
    // int snum;
    char *qbuf=NULL;
    char *tk;
    char *tv;
    char myname[HOST_NAME_MAX + 1];

    if((junk=gethostname(myname,HOST_NAME_MAX)) == -1 ){
        CCAE(1,"cannot determine my hostname");
    }
    tk=strdup("myname");
    tv=strdup(myname);
    updateConfig(tk,tv);
    DBGL("my name is %s",tv);
    length=snprintf(qbuf,0,"select id from %s where name='%s' and logfile='%s'",configValue("dbtableserver"),configValue("myname"),configValue("follow"));
    length++;
    if((qbuf=malloc(length))){
        length=snprintf(qbuf,length,"select id from %s where name='%s' and logfile='%s'",configValue("dbtableserver"),configValue("myname"),configValue("follow"));
        DBGL("query: %s",qbuf);
        if((junk=mysql_query(myconn,qbuf)) == 0 ){
            if((result=mysql_store_result(myconn))!=NULL){
                numrows=mysql_num_rows(result);
                if(numrows==0){
                    DBG("No server record yet.");
                    /* we don't have a record in the db yet, lets create one */
                    /* free the previous result set first though */
                    mysql_free_result(result);
                    /* reuse the qbuf, so free it first */
                    free(qbuf);
                    // snum=atoi(configValue("dbnumber"));
                    length=snprintf(qbuf,0,"insert into %s (name,logfile,environment,service,type,number) values ('%s','%s','%s','%s','%s',%s)",configValue("dbtableserver"),configValue("myname"),configValue("follow"),configValue("dbenv"),configValue("dbservice"),configValue("dbtype"),configValue("dbnumber"));
                    length++;
                    if((qbuf=malloc(length))){
                        length=snprintf(qbuf,length,"insert into %s (name,logfile,environment,service,type,number) values ('%s','%s','%s','%s','%s',%s)",configValue("dbtableserver"),configValue("myname"),configValue("follow"),configValue("dbenv"),configValue("dbservice"),configValue("dbtype"),configValue("dbnumber"));
                        DBGL("query: %s",qbuf);
                        if((junk=mysql_query(myconn,qbuf))==0){
                            umyid=mysql_insert_id(myconn);
                            DBGL("insert id: %llu",(unsigned long long)umyid);
                            // tjunk=(int) umyid;
                            /* reuse the qbuf one more time */
                            free(qbuf);
                            length=snprintf(qbuf,0,"%llu",(unsigned long long)umyid);
                            length++;
                            if((qbuf=malloc(length))){
                                length=snprintf(qbuf,length,"%llu",(unsigned long long)umyid);
                                DBGL("myid: %s",qbuf);
                                tk=strdup("myid");
                                tv=strdup(qbuf);
                                updateConfig(tk,tv);
                            }else{
                                CCAE(1,"out of memory allocating space for my server id");
                            }
                        }else{
                            CCAE(1,"error inserting my details into db");
                        }
                    }else{
                        CCAE(1,"cannot allocate memory for sub query");
                    }
                }else{
                    row=mysql_fetch_row(result);
                    tk=strdup("myid");
                    tv=strdup(row[0]);
                    DBGL("existing myid: %s",tv);
                    updateConfig(tk,tv);
                    mysql_free_result(result);
                }
            }
        }
        free(qbuf);
    }else{
        CCAE(1,"cannot allocate space for initial db query");
    }
}/* }}} */
void catchsignal(int sig)/* {{{1 */
{
    stopfollowing=1;
    DBG("in sig handler");
    // signal(sig,catchsignal);
} /* }}} */
int emitdata(struct SummaryLine *sl)/* {{{1 */
{
    char *query=NULL;
    char *summary;
    int length;
    int ret=1;

    if((summary=escapestr(sl->summary))){
        length=snprintf(query,0,"insert into %s (sid,stamp,linenumber,bytepos,stacktrace,summary) values (%s,%d,%ld,%ld,%d,'%s')",configValue("dbtablelog"),configValue("myid"),(int)sl->linetime,sl->linenumber,sl->fpos,sl->numstacks,summary);
        length++;
        if(query=malloc(length)){
            length=snprintf(query,length,"insert into %s (sid,stamp,linenumber,bytepos,stacktrace,summary) values (%s,%d,%ld,%ld,%d,'%s')",configValue("dbtablelog"),configValue("myid"),(int)sl->linetime,sl->linenumber,sl->fpos,sl->numstacks,summary);
            if((ret=mysql_query(myconn,query) != 0)){
                DBGL("failed to insert line into db: %s",query);
            }
            free(query);
        }else{
            CCAE(1,"cannot allocate memory for emitdata query string");
        }
        free(summary);
    }
    return ret;
}
int emitdataprintf(struct SummaryLine *sl)/* {{{1 */
{
    char *buffer=NULL;
    char *lnbuf;
    char *ltbuf;
    char *pbuf;
    char *sbuf;
    int length=0;
    time_t linetime;

    // struct tm *curDate;
    char *spbuf=NULL;
    if( (spbuf=malloc(27)) == NULL){
        CCAC("Failed to allocate memory for building output string 5.");
    }else{
        linetime=sl->linetime;
        spbuf=ctime_r(&linetime,spbuf);
        spbuf=trim(spbuf);

        ltbuf=numtostr((long long)linetime);
        if(ltbuf){
            if((pbuf=numtostr((long long) sl->fpos))){
                if((sbuf=numtostr((long long) sl->numstacks))){
                    if((lnbuf=numtostr((long long) sl->linenumber))){
                        /* work out the length of buffer needed for the output string */
                        length=snprintf(buffer,0,"%s,%s,%s,%s,%s,%s",spbuf,ltbuf,lnbuf,pbuf,sbuf,sl->summary);
                        /* length=snprintf(buffer,0,"%s,%s,%s,%s",ltbuf,pbuf,sbuf,summary); */
                        length++;
                        if((buffer=malloc(length))){
                            length=snprintf(buffer,length,"%s,%s,%s,%s,%s,%s",spbuf,ltbuf,lnbuf,pbuf,sbuf,sl->summary);
                            /* length=snprintf(buffer,length,"%s,%s,%s,%s",ltbuf,pbuf,sbuf,summary); */
                            printf("%s\n",buffer);
                            free(buffer);
                        }else{
                            length=0;
                            CCAC("Failed to allocate memory for building output string 4.");
                        }
                        free(sbuf);
                    }else{
                        CCAC("Failed to allocate memeory for building output string 7.");
                    }
                    free(lnbuf);
                }else{
                    CCAC("Failed to allocate memory for building output string 3.");
                }
                free(pbuf);
            }else{
                CCAC("Failed to allocate memory for building output string 2.");
            }
            free(ltbuf);
        }else{
            CCAC("Failed to allocate memory for building output string 1.");
        }
        free(spbuf);
    }
    return length;
}/* }}} */
void usage( void )/* {{{1 */
{
    fprintf(stdout,"qillsd [options]\n");
    fprintf(stdout,"    -c path-to-configuration-file (default /etc/qillsd.conf)\n");
    fprintf(stdout,"    -h this message\n");
    fprintf(stdout,"\n");
}/* }}} */
void daemonize()/* {{{1 */
{
    int i,lfp;
    char str[MAX_MSG_LEN];
    int junk;

    if(getppid()==1) return; /* already a daemon */
    DBG("Forking");
    i=fork();
    if (i<0) 
    {
        CCAE(1,"fork failed. Exiting");
    }
    if (i>0) 
    {
        DBG("parent exiting after fork");
        exit(0);
    } /* parent exits */
    /* child (daemon) continues */
    DBG("fork success. Child process continues.");
    setsid(); /* obtain a new process group */
    DBG("closing file descriptors");
    /* Close out the standard file descriptors */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    DBG("redirecting standard i/o to /dev/null");
    i=open("/dev/null",O_RDWR); dup(i); dup(i); /* handle standard I/O */
    DBG("setting umask to 027");
    umask(027); /* set newly created file permissions */
    DBGL("cd'ing to %s",CCA_HOME);
    chdir(CCA_HOME); /* change running directory */
    DBG("Creating lock file");
    lfp=open(CCA_LOCK_FILE,O_RDWR|O_CREAT,0640);
    if (lfp<0) {
        CCAE(1,"Failed to create lock file, exiting.");
    } /* can not open */
    if (lockf(lfp,F_TLOCK,0)<0) {
        CCAE(1,"Cannot lock, another instance running? qitting");
    } /* can not lock */
    DBG("File locked.");
    /* first instance continues */
    sprintf(str,"%d",getpid());
    write(lfp,str,strlen(str)); /* record pid to lockfile */
    DBG("pid written to lock file");
    DBG("setting signal handlers");
    /*
    sigemptyset(siga->sa_mask);
    sigaddset(siga->sa_mask,SIGCHLD);
    sigaddset(siga->sa_mask,SIGTSTP);
    sigaddset(siga->sa_mask,SIGTTOU);
    sigaddset(siga->sa_mask,SIGTTIN);
    */
    siga->sa_handler=SIG_IGN;
    if((junk=sigaction(SIGCHLD,siga,NULL))!=0){
        CCAE(1,"cannot ignore signal SIGCHLD");
    }
    siga->sa_handler=SIG_IGN;
    if((junk=sigaction(SIGTSTP,siga,NULL))!=0){
        CCAE(1,"cannot ignore signal SIGTSTP");
    }
    siga->sa_handler=SIG_IGN;
    if((junk=sigaction(SIGTTOU,siga,NULL))!=0){
        CCAE(1,"cannot ignore signal SIGTTOU");
    }
    siga->sa_handler=SIG_IGN;
    if((junk=sigaction(SIGTTIN,siga,NULL))!=0){
        CCAE(1,"cannot ignore signal SIGTTIN");
    }
    /*
       siga->sa_handler=catchsignal;
       if((junk=sigaction(SIGHUP,siga,NULL))!=0){
       CCAE(1,"cannot set handler for SIGHUP");
       }
       siga->sa_handler=catchsignal;
       if((junk=sigaction(SIGTERM,siga,NULL))!=0){
       CCAE(1,"cannot set handler for SIGHUP");
       }
       */
    siga->sa_handler=catchsignal;
    if((junk=sigaction(SIGHUP,siga,NULL))!=0){
        CCAE(1,"cannot set handler for SIGHUP");
    }
    siga->sa_handler=catchsignal;
    if((junk=sigaction(SIGTERM,siga,NULL))!=0){
        CCAE(1,"cannot set handler for SIGTERM");
    }
    siga->sa_handler=catchsignal;
    if((junk=sigaction(SIGINT,siga,NULL))!=0){
        CCAE(1,"cannot set handler for SIGINT");
    }
    siga->sa_handler=catchsignal;
    if((junk=sigaction(SIGUSR1,siga,NULL))!=0){
        CCAE(1,"cannot set handler for SIGUSR1");
    }
    DBG("qills daemonized");
}/* }}} */
void setDefaultConfig( void )/* {{{1 */
{
    char *tk;
    char *tv;

    tk=strdup("daemonize");
    tv=strdup(CCA_DEFAULT_DAEMONIZE);
    updateConfig(tk,tv);
    tk=strdup("configfile");
    tv=strdup(CCA_DEFAULT_CONFIGFILE);
    updateConfig(tk,tv);
    tk=strdup("summarysize");
    // DBGL("summarysize kp:%p",tk);
    tv=strdup(CCA_DEFAULT_SUMMARY_SIZE);
    // DBGL("summarysize vp:%p",tv);
    updateConfig(tk,tv);
    tk=strdup("buffersize");
    tv=strdup(CCA_DEFAULT_BUFFERSIZE);
    updateConfig(tk,tv);
    tk=strdup("sleeptime");
    tv=strdup(CCA_DEFAULT_SLEEPTIME);
    updateConfig(tk,tv);
    tk=strdup("summarize");
    tv=strdup(CCA_DEFAULT_SUMMARIZE);
    // DBGL("summarize: %p",tv);
    updateConfig(tk,tv);
    tk=strdup("fpos");
    tv=strdup(CCA_DEFAULT_FPOS);
    updateConfig(tk,tv);
    tk=strdup("timeformat");
    tv=strdup(CCA_DEFAULT_TIME_FORMAT);
    updateConfig(tk,tv);
    tk=strdup("search");
    tv=strdup(CCA_DEFAULT_SEARCH);
    updateConfig(tk,tv);
    tk=strdup("filebin");
    tv=strdup(CCA_DEFAULT_FILEBIN);
    updateConfig(tk,tv);
    tk=strdup("filebinargs");
    tv=strdup(CCA_DEFAULT_FILEBINARGS);
    updateConfig(tk,tv);
    tk=strdup("gunzipbin");
    tv=strdup(CCA_DEFAULT_GUNZIPBIN);
    updateConfig(tk,tv);
    tk=strdup("gunzipbinargs");
    tv=strdup(CCA_DEFAULT_GUNZIPBINARGS);
    updateConfig(tk,tv);
    tk=strdup("unzipbin");
    tv=strdup(CCA_DEFAULT_UNZIPBIN);
    updateConfig(tk,tv);
    tk=strdup("unzipbinargs");
    tv=strdup(CCA_DEFAULT_UNZIPBINARGS);
    updateConfig(tk,tv);
    tk=strdup("indexrotatedlogfiles");
    tv=strdup(CCA_DEFAULT_INDEXROTATEDLOGFILES);
    updateConfig(tk,tv);
    tk=strdup("missinglogfiles");
    tv=strdup(CCA_DEFAULT_MISSINGFILES);
    updateConfig(tk,tv);
    tk=strdup("rotatedlogfileext");
    tv=strdup(CCA_DEFAULT_RLFEXT);
    updateConfig(tk,tv);
    tk=strdup("dbhost");
    tv=strdup(CCA_DEFAULT_DBHOST);
    updateConfig(tk,tv);
    tk=strdup("dbdb");
    tv=strdup(CCA_DEFAULT_DBDB);
    updateConfig(tk,tv);
    tk=strdup("dbtableserver");
    tv=strdup(CCA_DEFAULT_DBTABLE_SERVER);
    updateConfig(tk,tv);
    tk=strdup("dbtablelog");
    tv=strdup(CCA_DEFAULT_DBTABLE_LOG);
    updateConfig(tk,tv);
    tk=strdup("dbuser");
    tv=strdup(CCA_DEFAULT_DBUSER);
    updateConfig(tk,tv);
    tk=strdup("dbpass");
    tv=strdup(CCA_DEFAULT_DBPASS);
    updateConfig(tk,tv);
    tk=strdup("dbenv");
    tv=strdup(CCA_DEFAULT_DBENV);
    updateConfig(tk,tv);
    tk=strdup("dbservice");
    tv=strdup(CCA_DEFAULT_DBSERVICE);
    updateConfig(tk,tv);
    tk=strdup("dbtype");
    tv=strdup(CCA_DEFAULT_DBTYPE);
    updateConfig(tk,tv);
    tk=strdup("dbnumber");
    tv=strdup(CCA_DEFAULT_DBNUMBER);
    updateConfig(tk,tv);
}/* }}} */
int processOptions(int argc, char **argv)/* {{{1 */
{
    int opt;
    int ret=1;
    char *toptarg;
    char *tkey;

    while((opt = getopt(argc, argv, "c:Dh")) != -1){
        switch(opt){
            case 'c':
                DBGL("Setting configfile to %s",optarg);
                if(( toptarg=strdup(optarg)) == NULL){
                    CCAE(1,"Error allocating memory for optarg (config file)");
                }
                if(( tkey=strdup("configfile")) == NULL){
                    CCAE(1,"Error allocating memory for key 'configfile'");
                }
                updateConfig(tkey,toptarg);
                break;
            case 'D':
                DBG("Not running in daemon mode");
                if((tkey=strdup("daemonize"))==NULL){
                    CCAE(2,"error allocating memory");
                }
                if((toptarg=strdup("0"))==NULL){
                    CCAE(2,"error allocating memory");
                }
                updateConfig(tkey,toptarg);
                break;
            case 'h':
                usage();
                ret=0;
                break;
        }
    }
    return ret;
}/* }}} */
int main(int argc, char **argv)/* {{{1 */
{
    int junk;
    struct FollowedFile *ff;
    long fpos;
    char **rlfnames=NULL;

    if((junk = initConfig()) == 0){
        if((siga=malloc(sizeof(struct sigaction)))==NULL){
            CCAE(1,"out of memory");
        }
        siga->sa_handler=catchsignal;
        // sigemptyset(siga->sa_mask);
        siga->sa_flags=0;
        setDefaultConfig();
        if((junk=processOptions(argc,argv))==1){
            if(junk=atoi(configValue("daemonize"))==1){
                daemonize();
            }else{
                if((junk=sigaction(SIGHUP,siga,NULL))!=0){
                    CCAE(1,"cannot set handler for SIGHUP");
                }
                siga->sa_handler=catchsignal;
                if((junk=sigaction(SIGTERM,siga,NULL))!=0){
                    CCAE(1,"cannot set handler for SIGTERM");
                }
                siga->sa_handler=catchsignal;
                if((junk=sigaction(SIGINT,siga,NULL))!=0){
                    CCAE(1,"cannot set handler for SIGINT");
                }
                siga->sa_handler=catchsignal;
                if((junk=sigaction(SIGUSR1,siga,NULL))!=0){
                    CCAE(1,"cannot set handler for SIGUSR1");
                }
            }
            DBGL("Getting config from %s",configValue("configfile"));
            getConfigFromFile( configValue("configfile") );
            DBGL("will follow %s looking for '%s'\n",configValue("follow"),configValue("search"));
            DBGL("Setting up mysql connect to %s",configValue("dbhost"));
            if((myconn = mysql_init(NULL)) == NULL ){
                CCAE(1,"cannot initialise connection to mysql libraries.");
            }
            DBGL("connecting to mysql: %s, db: %s, user: %s, pass: %s",configValue("dbhost"),configValue("dbdb"),configValue("dbuser"),configValue("dbpass"));
            if(mysql_real_connect(myconn,configValue("dbhost"),configValue("dbuser"),configValue("dbpass"),configValue("dbdb"),0,NULL,0)==NULL){
                CCAE(1,"cannot connect to mysql server.");
            }
            DBG("obtaining unique server id");
            setLogServerId();
            setLastPosition();
            fpos=atol(configValue("lastbytepos"));
            if(( ff=setupfollowfile(fpos)) != NULL){
                DBG("follow file setup ok\n");
                /* whether to parse rotated log files first */
                domissinglogfiles=atoi(configValue("missinglogfiles"));
                if(domissinglogfiles==1){
                    if(rlfnames==NULL){
                        DBG("Getting names of rotated log files\n");
                        rlfnames=getRotatedLogFileNames(ff->filename);
                    }
                    DBG("parsing missing log files.\n");
                    parseRotatedLogs(ff,rlfnames);
                }else{
                    if((junk=atoi(configValue("indexrotatedlogfiles")))==1){
                        if(rlfnames==NULL){
                            DBG("Getting names of rotated log files\n");
                            rlfnames=getRotatedLogFileNames(ff->filename);
                        }
                        indexRotatedLogs(rlfnames);
                    }
                }
                if(rlfnames){
                    DBG("freeing log file names\n");
                    freeRotatedLogFileNames(rlfnames);
                }
                DBG("Following main file");
                while(1){
                    if((junk=followfile(ff))==1){;
                        DBG("followed file has been rotated");
                        /*
                         * if junk==1 then file has shrunk
                         */
                        /*
                         * don't bother reading the end of the file
                         *
                        junk=finishReadingRotatedFile(ff,1);
                        DBGL("finished reading rotated file: junk is %d",junk);
                        */
                        /*
                         * have now finished reading the file
                         * after it has been rotated
                         * now restart reading the new
                         * log file
                         */
                        freeffile(ff);
                        /* sleep for 2 minutes to allow time for the new log file to be populated */
                        sleep(120);
                        if(( ff=setupfollowfile(0)) == NULL){
                            /* failed to set up the new file */
                            CCAC("Failed to setup new log file after rotation");
                            junk=253;
                            break;
                        }
                    }else{
                        break;
                    }
                }
            }else{
                junk=254;
            }
            DBGL("finished following file because %d",junk);
            junk=0;
            if(ff){
                freeffile(ff);
            }
            DBG("freeing mysql library stuff");
            mysql_close(myconn);
        }
        DBG("freeing rotated log file indexes.");
        // printRotatedLogs();
        freeRotatedLogFiles();
        DBG("deleting config");
        deleteConfig();
        DBG("Freeing signal handling struct.");
        free(siga);
    }else{
        CCAE(1,"Cannot initialise configuration.");
    }
    DBG("Bye!");
    return junk;
}/* }}} */
